/*
 * spcp.c
 *
 *  Created on: 2018年1月2日
 *      Author: xiaotao
 */

//simple_peripherals_communication_protocol

#include "spcp.h"

static int coding8(uint8_t src, uint8_t dst[], size_t dst_len);
static int coding16(uint16_t src, uint8_t dst[], size_t dst_len);
static uint8_t decoding8(const uint8_t src[]);
static uint16_t decoding16(const uint8_t src[]);

/*
 cache          初始化后不允许修改，且每次调用需要是同一个变量，与spcp_write 中的也是同一个变量
 buf            要传输的内容
 count          要传输的内容的字节数
 timeout        仅针对w_handler中的超时,如果要对spcp_write进行超时,需要在其他地方实现

 返回buf成功发送的字节数，小于0表示错误
*/
ssize_t spcp_write(spcp_cache_t *cache, const void *buf, size_t count, struct timeval *timeout)
{
        const uint8_t *val = (const uint8_t *) buf;
        size_t maxsize = 0, length = 0, i = 0;
        uint8_t tmp[4] = {0};
        ssize_t nbytes = 0;
        int err = 0;

        if (count == 0)
                return 0;

        length = SPCP_HEADER_LEN + count;
        if (cache == NULL || buf == NULL || length > 0xFFFF) {
                err = -EFAULT;
                goto err0;
        }

        maxsize = length * 2 - 1; //tag 不用分拆成2个字节,其他都一拆二

        if (maxsize > sizeof(cache->w_buf)) {
                cache->w_ptr = calloc_handler(maxsize, sizeof(uint8_t), &cache->calloc_timeout);
                if (cache->w_ptr == NULL) {
                        err = -ENOMEM;
                        goto err0;
                }
        } else
                cache->w_ptr = cache->w_buf;

        //header 赋值
        cache->w_ptr[0] = SPCP_TAG; //tag 不用转换
        cache->w_ptr[1] = 0;        //reserve
        cache->w_ptr[2] = 0;        //reserve
        coding16(my_hton16((uint16_t) length), tmp, sizeof(tmp));
        cache->w_ptr[3] = tmp[0];
        cache->w_ptr[4] = tmp[1];
        cache->w_ptr[5] = tmp[2];
        cache->w_ptr[6] = tmp[3];
        cache->w_ptr[7] = 0;  //crc16
        cache->w_ptr[8] = 0;  //crc16
        cache->w_ptr[9] = 0;  //crc16
        cache->w_ptr[10] = 0; //crc16

        //spcp_t 中的 val 赋值
        for (i = 0; i < count; ++i) {
                coding8(val[i], tmp, sizeof(tmp));
                cache->w_ptr[i * 2 + sizeof(cache->r_buf4header) + 0] = tmp[0];
                cache->w_ptr[i * 2 + sizeof(cache->r_buf4header) + 1] = tmp[1];
        }

        //计算 crc16
        coding16(my_hton16(my_crc16_ccitt(cache->w_ptr, maxsize)), tmp, sizeof(tmp));
        cache->w_ptr[7] = tmp[0];  //crc16
        cache->w_ptr[8] = tmp[1];  //crc16
        cache->w_ptr[9] = tmp[2];  //crc16
        cache->w_ptr[10] = tmp[3]; //crc16

        nbytes = cache->w_handler(cache->fd, cache->w_ptr, maxsize, timeout);
        if (maxsize > sizeof(cache->w_buf)) {
                free_handler(cache->w_ptr);
        }
        if (nbytes < 0) {
                err = nbytes;
                goto err0;
        } else if (nbytes == 0) {
                err = -ENODATA;
                goto err0;
        } else if (nbytes != maxsize) {
                err = -EIO;
                goto err0;
        }

        return count;
err0:
        return err;
}

/*
 在轮询中使用
 cache          初始化后不允许修改，且每次调用需要是同一个变量，与spcp_write 中的也是同一个变量
 buf            接收数据的缓存区
 count          接收数据的缓存区的字节数
 timeout        仅针对w_handler中的超时,如果要对 spcp_read_poll 进行超时,需要在其他地方实现

 返回接收数据的字节数，小于0表示错误
*/
ssize_t spcp_read_poll(spcp_cache_t *cache, void *buf, size_t count, struct timeval *timeout)
{
        uint8_t *val = (uint8_t *) buf;
        ssize_t nbytes = 0;
        size_t spcp_len = 0;
        uint8_t tmp = 0;
        uint16_t crc16 = 0;
        int err = 0;

        if (cache == NULL || buf == NULL || count == 0) {
                err = -EFAULT;
                goto err0;
        }

        nbytes = cache->r_handler(cache->fd, &tmp, sizeof(tmp), timeout);
        if (nbytes < 0) {
                err = nbytes;
                goto err0;
        } else if (nbytes == 0) {
                err = -ENODATA;
                goto err0;
        } else if (nbytes != sizeof(tmp)) {
                err = -EIO;
                goto err0;
        }

        switch (cache->r_fsm) {
        case 0:
                if (tmp == SPCP_TAG) {
                        cache->r_buf_cur = 0;
                        cache->r_buf4header[cache->r_buf_cur++] = tmp;
                        cache->r_fsm = 1;
                }
                break;
        case 1:
                if (tmp == SPCP_TAG) {
                        cache->r_buf_cur = 0;
                        cache->r_buf4header[cache->r_buf_cur++] = tmp;
                        //cache->r_fsm = 1;
                        break;
                }

                cache->r_buf4header[cache->r_buf_cur++] = tmp;
                if (cache->r_buf_cur < sizeof(cache->r_buf4header))
                        break;

                spcp_len = my_ntoh16(decoding16(&cache->r_buf4header[3]));
                cache->r_val_len = spcp_len - SPCP_HEADER_LEN;
                if (cache->r_val_len > count) {
                        err = -EOVERFLOW;
                        goto err0;
                }

                cache->r_buf_tot_size = spcp_len * 2 - 1;
                if (cache->r_buf_tot_size > sizeof(cache->r_buf)) {
                        if (cache->is_r_buf_calloc) {
                                free_handler(cache->r_ptr);
                                cache->is_r_buf_calloc = false;
                        }

                        cache->r_ptr = calloc_handler(cache->r_buf_tot_size, sizeof(uint8_t), &cache->calloc_timeout);
                        if (cache->r_ptr == NULL) {
                                err = ENOMEM;
                                goto err0;
                        }
                        cache->is_r_buf_calloc = true;
                } else
                        cache->r_ptr = cache->r_buf;

                cache->r_crc16 = my_ntoh16(decoding16(&cache->r_buf4header[7]));
                cache->r_buf4header[7] = 0; //crc16
                cache->r_buf4header[8] = 0;
                cache->r_buf4header[9] = 0;
                cache->r_buf4header[10] = 0;
                memcpy(cache->r_ptr, cache->r_buf4header, sizeof(cache->r_buf4header));

                cache->r_fsm = 2;
                break;
        case 2:
                if (tmp == SPCP_TAG) {
                        cache->r_buf_cur = 0;
                        cache->r_buf4header[cache->r_buf_cur++] = tmp;
                        cache->r_fsm = 1;

                        if (cache->is_r_buf_calloc) {
                                free_handler(cache->r_ptr);
                                cache->is_r_buf_calloc = false;
                        }
                        break;
                }

                cache->r_ptr[cache->r_buf_cur++] = tmp;
                if (cache->r_buf_cur < cache->r_buf_tot_size)
                        break;

                crc16 = my_crc16_ccitt(cache->r_ptr, cache->r_buf_tot_size);
                cache->r_fsm = 0;

                if (crc16 == cache->r_crc16)
                        goto success;
                else
                        goto failed;

                break;
        default:
                err = -EPFNOSUPPORT;
                goto err0;
                break;
        }

        return -EAGAIN;
err0:
        return err;
success : {
        memset(buf, 0, count);
        for (size_t i = 0; i < cache->r_val_len; ++i) {
                val[i] = decoding8(&cache->r_ptr[i * 2 + sizeof(cache->r_buf4header)]);
        }
        if (cache->is_r_buf_calloc) {
                free_handler(cache->r_ptr);
                cache->is_r_buf_calloc = false;
        }
        return cache->r_val_len;
}
failed : {
        if (cache->is_r_buf_calloc) {
                free_handler(cache->r_ptr);
                cache->is_r_buf_calloc = false;
        }
        return -1;
}
}

static int coding8(uint8_t src, uint8_t dst[], size_t dst_len)
{
        if (dst == NULL || dst_len < 2) {
                goto err0;
        }

        dst[0] = src >> 4 & 0xF;
        dst[1] = src & 0xF;

        return 0;
err0:
        return -1;
}

static int coding16(uint16_t src, uint8_t dst[], size_t dst_len)
{
        uint8_t i = 0;
        const uint8_t shift[4] = {12, 8, 4, 0};

        if (dst == NULL || dst_len < 4) {
                goto err0;
        }

        for (i = 0; i < 4; ++i) {
                dst[i] = src >> shift[i] & 0xF;
        }

        return 0;
err0:
        return -1;
}

static uint8_t decoding8(const uint8_t src[])
{
        return (src[0] << 4 | src[1]) & 0xFF;
}

static uint16_t decoding16(const uint8_t src[])
{
        return (src[0] << 12 | src[1] << 8 | src[2] << 4 | src[3]) & 0xFFFF;
}
